#ifndef SC_CONFIG_H_INCLUDED
#define SC_CONFIG_H_INCLUDED

#include "libxml/parser.h"
#include "libxml/xpath.h"

#include <string>
#include <list>
#include <map>

#include <boost/lexical_cast.hpp>


namespace sc {

namespace cfg {


/**
* @class Attribute
*/
class Attribute
{
public:
    enum Type { EDIT, CHOICE };
    typedef std::list<std::string> TChoices;
    typedef std::map<std::string,std::string> TParameters;

private:
    std::string  m_sName;
    Type         m_type;
    std::string  m_sType;
    TChoices     m_choices;
    TParameters  m_parameters;

public:
    Attribute(const std::string& sName)
    : m_sName(sName), m_type(EDIT) { }

    const std::string& GetName(void) const { return m_sName; }
    Type GetType(void) const { return m_type; }
    const std::string& GetTypeStr(void) const { return m_sType; }
    const TChoices& GetChoices(void) const { return m_choices; }
    const TParameters& GetParameters(void) const { return m_parameters; }

    void SetType(const std::string& sType)
    {
        m_sType = sType;
    }

    void SetType(Type type)
    {
        m_type = type;
    }

    bool AddChoice(const std::string& sChoice)
    {
        if (m_type != CHOICE)
            return false;

        m_choices.push_back(sChoice);
        return true;
    }

    bool AddParameter(const std::string& sName, const std::string& sValue)
    {
        if (m_type == CHOICE)
            return false;

        return m_parameters.insert( std::make_pair(sName, sValue) ).second;
    }

    bool Validate(const std::string& sValue) const;

    template<class T>
    bool ValidateParams(const std::string& sValue) const;
};

typedef std::list<Attribute>  TAttributes;


/**
* Element
*/
class Element;
typedef std::map<std::string,Element>  TElements;
typedef std::list<TElements::iterator> TChildren;

class Element
{
public:
    enum Type { UNIQUE, OPTION, MULTIPLE };

private:
    std::string   m_sName;
    Type          m_type;
    TChildren     m_children;
    TAttributes   m_attributes;

public:
    Element(const std::string& sName, Type type = UNIQUE)
    : m_sName(sName), m_type(type) { }

    const std::string& GetName(void) const { return m_sName; }
    Type GetType(void) const { return m_type; }
    const TChildren& GetChildren(void) const { return m_children; }
    const TAttributes& GetAttributes(void) const { return m_attributes; }

    bool AddChild(TElements::iterator& rChild)
    {
        m_children.push_back(rChild);
        return true;
    }

    bool AddAttribute(const Attribute& rcAttribute)
    {
        m_attributes.push_back(rcAttribute);
        return true;
    }
};

}; // namespace cfg


/**
* @class Config
*
* This class represent the grammar of the Setup. It is based on Relax NG document.
*/
class Config
{
protected:
    std::string        m_sFilename;  //!< File name of the RNG file

    xmlDocPtr          m_rngDoc;     //!< xml document of the RNG file
    xmlXPathContextPtr m_xpathCtx;   //!< XPath context

    cfg::TElements     m_elements;   //!< All available Element

public:
    Config(void);
    ~Config(void);

public:
    int LoadRng(const std::string& sFile);

    int GetRoot(std::string& sRoot) const;
    const cfg::TElements& GetElements(void) const { return m_elements; }
    cfg::TElements& GetElements(void) { return m_elements; }

    void Clear(void);

    static bool IsRngValid(const std::string& sFileRNG);

protected:
    int GetChildren(cfg::TElements::iterator iterRoot);
    int GetAttribute(cfg::Element& rElement, xmlNodePtr node);
    int GetAttributeValues(cfg::Attribute& rAttribute, xmlNodePtr node);
	int GetAttributeData(cfg::Attribute& rAttribute, xmlNodePtr node);
	int GetAttributeChoice(cfg::Attribute& rAttribute, xmlNodePtr node);
	int GetAttributeParam(cfg::Attribute& rAttribute, xmlNodePtr node);

	int GetChildren(cfg::TElements::iterator iterRoot, xmlNodePtr node, cfg::Element::Type type);
	int GetElement(cfg::TElements::iterator parent, xmlNodePtr node, cfg::Element::Type type);

    xmlNodePtr GetRefNode(xmlNodePtr node) const;

    /**
    * @class PredFindAttributeByName
    */
    class PredFindAttributeByName
    {
    private:
        std::string m_sName;

    public:
        PredFindAttributeByName(const std::string& sName)
        : m_sName(sName)
        { }

        bool operator() (const cfg::Attribute& rcAttribute)
        {
            return m_sName == rcAttribute.GetName();
        }
    };
};

/**
* @class Validator
*/
template<class T>
class Validator
{
private:
    T m_value;

public:
    Validator(const T& value)
    : m_value(value)
    {
    }

    /**
    * Validate by comparing the reference (sRef) to the value with the comparison function.
    *  C is a binary function, can be std::less<T>, std::greater<T>, ...
    *
    * @param sRef
    * @param compare
    *
    * @return true if the comparison between sValue and sRef is true
    */
    template <class C>
    bool IsOk(const std::string& sRef, C compare)
    {
        bool bValid = false;
        try
        {
            bValid = compare(m_value, boost::lexical_cast<T>(sRef));
        }
        catch (boost::bad_lexical_cast& ) { }
        return bValid;
    }
};

}; // namespace sc

// Stream functions
std::ostream& operator<<(std::ostream& os, const sc::cfg::Element& rcElement);

std::ostream& operator<<(std::ostream& os, const sc::cfg::Attribute& rcAttribute);

#endif // SC_CONFIG_H_INCLUDED
